package com.pic.mgt.telnetd;

import java.io.IOException;
import java.net.Socket;
import java.util.LinkedList;
import java.util.List;

import net.wimpi.telnetd.io.BasicTerminalIO;
import net.wimpi.telnetd.io.toolkit.BufferOverflowException;
import net.wimpi.telnetd.io.toolkit.Editfield;
import net.wimpi.telnetd.io.toolkit.InputFilter;
import net.wimpi.telnetd.io.toolkit.Label;
import net.wimpi.telnetd.net.Connection;
import net.wimpi.telnetd.net.ConnectionData;
import net.wimpi.telnetd.net.ConnectionEvent;
import net.wimpi.telnetd.shell.Shell;

import com.pic.mgt.Launcher;
import com.pic.mgt.log.IgxLog;

public class IGXShell implements Shell {
	
	private Connection connection;
	private BasicTerminalIO basicTerminalIO;
	
	protected final String newLine = "\r\n";
	
	private boolean done;

	private ShellCommandHandler shellCommandHandler;
	
	private List<String> shellCmdCacheList = new LinkedList<String>();
	private int shellCmdCachePos = -1;
	private final int SHELL_CMD_SIZE = 10;

	@Override
	public void run(Connection connection) {
		this.connection = connection;
		this.basicTerminalIO = connection.getTerminalIO();
		this.connection.addConnectionListener(this);
		
		String hostName = connection.getConnectionData().getHostName();
		String hostAddress =  connection.getConnectionData().getHostAddress();
		String handleThreadId = String.valueOf(connection.getId());

		// ********************** Log **********************
		IgxLog.messageLog.info(this,
						"A client logged in console. (host name:'{}' host address:'{}' process id:'{}'",
						hostName, hostAddress, handleThreadId);
		// ********************** Log **********************
		
		try {
			// 清空畫面並把游標移到home的位置
			// throws IOException
			this.basicTerminalIO.eraseScreen();
			// throws IOException
			this.basicTerminalIO.homeCursor();
			
			// 輸出 Title 訊息
			// throws IOException
			this.basicTerminalIO.write(this.newLine);
			this.basicTerminalIO.write("******* ");
			this.basicTerminalIO.write(TelnetD.consoleTitleStr);
			this.basicTerminalIO.write(" *******" + this.newLine + this.newLine);
			// throws IOException
			this.basicTerminalIO.flush();
			
			// -----------------------------------------------------------------
			
			
			// 指令輸入前置提示字串
			// (char)18 字元是不會顯示於螢幕中的控制字元,用來給 terminal 知道可以輸入指令了.
			Label commandPrefixHintLable = new Label(
					this.basicTerminalIO, "CommandEdit",
					TelnetD.defaultCommandPrefixHintStr + " console> " + (char)18);
			
			// 建立 Edit Field 給使用者輸入用
			Editfield editField = getCommandEditField();
			
			this.done = false;
			while(!done) {
				// throws IOException
				commandPrefixHintLable.draw();
				// throws IOException
				try {
					editField.run(); // 會停留在此
				} catch (IOException e) {
					// 如果在此發生 IOException，應該 Client 不正常斷線所造成，不需寫 log
					break;
				}
			}

			// 若迴圈結束表示端末將要斷線
			
			closeConnection();
			
			// ********************** Log **********************
			IgxLog.messageLog.info(this,
							"A client logged out from console. (host name:'{}' host address:'{}' process id:'{}'",
							hostName, hostAddress, handleThreadId);
			// ********************** Log **********************
			
		} catch (IOException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************
			
			// ********************** Log **********************
			IgxLog.messageLog.error(this,
							"SUDA-312 TelnetD service occured an I/O error in processing terminal({}:{}) shell.",
							hostName,
							hostAddress);
			// ********************** Log **********************
			
			closeConnection();
		}
	}
	
	private Editfield getCommandEditField() {
		final Editfield editField = new Editfield(this.basicTerminalIO, "CommandEdit", 200);
		editField.registerInputFilter(new InputFilter() {
			@Override
			public int filterInput(int key) throws IOException {
				// 10 表示按下 Enter
				if (key == 10) {
					shellCmdCachePos = -1;
					
					basicTerminalIO.write(newLine);
					
					String commandStr = editField.getValue().trim();
					editField.clear();
					
					if (commandStr.length() > 0) {
						// 先放入 shecll command cache list 中
						if (shellCmdCacheList.size() == SHELL_CMD_SIZE) {
							shellCmdCacheList.remove(shellCmdCacheList.size() - 1);
						}
						shellCmdCacheList.add(0, commandStr);
						
						
						if (commandStr.equals("help")) {
							String helpMessage = shellCommandHandler.getCommandHelpDescription();
							basicTerminalIO.eraseLine();
							basicTerminalIO.write(helpMessage);
						}
						else {
							if (commandStr.equals("exit")) {
								done = true;
								basicTerminalIO.write("******* Terminated " + TelnetD.consoleTitleStr + ". *******" + newLine);
							}
							else {
								String replyMessage = shellCommandHandler.handleCommand(commandStr);
								basicTerminalIO.write(replyMessage + newLine);
							}
						}
					}
					
					// (char)16 表示指令訊息回應結束
					basicTerminalIO.write(newLine + (char)16);
				}
				else if(key == 1001 || key == 1002) {
					// '上'鍵 或 '下'鍵
					
					String cachedShellCmd = getShellCmdCache(key);
					if (null != cachedShellCmd) {
						try {
							editField.setValue(cachedShellCmd);
						} catch (BufferOverflowException e) {
							throw new IOException("", e);
						}
					}
					return InputFilter.INPUT_HANDLED;
				}
				else if (key == 1301) {
					// tab 鍵
					
					String commandPrefixStr = editField.getValue().trim();
					
					String matchedCommandStr =
						shellCommandHandler.getMatchedCommandByGivenPrefix(commandPrefixStr);
					if (null != matchedCommandStr) {
						try {
							editField.setValue(matchedCommandStr);
						} catch (BufferOverflowException e) {
							throw new IOException("", e);
						}
					}
						
					return InputFilter.INPUT_HANDLED;
				}
				
				// 若user輸入為 Enter (key為10)則，EditField會結束掉
				return key;
			}
		});
		
		return editField;
	}
	
	/**
	 * 根據使用者按下 '上或下' 鍵,取得先前輸入的 shell command .
	 * @param key
	 * @return 若回傳為 null, 表示沒有先前輸入的記錄
	 */
	private String getShellCmdCache(int key) {
		if (this.shellCmdCacheList.isEmpty()) {
			return null;
		}
		else {
			int size = this.shellCmdCacheList.size();
			
			if (key == 1001) {
				// 上鍵
				this.shellCmdCachePos++;
				if (this.shellCmdCachePos > size - 1) {
					this.shellCmdCachePos--;
					return null;
				}
				else {
					return this.shellCmdCacheList.get(this.shellCmdCachePos);
				}
			}
			else {
				// 下鍵
				this.shellCmdCachePos--;
				if (this.shellCmdCachePos < 0) {
					this.shellCmdCachePos++;
					return null;
				}
				else {
					return this.shellCmdCacheList.get(this.shellCmdCachePos);
				}
			}
		}
	}
	
	/**
	 * 此方法必須要存在，因為 TelnetD 服務在有連線進來時，
	 * 會呼叫此方法來處理 shell.
	 * @return
	 */
	public static Shell createShell() {
		// 每次都 new 一個 instance 回傳
		return
			Launcher.applicationContext.getBean("igxShell", IGXShell.class);
	}
	
	@Override
	public void connectionIdle(ConnectionEvent arg0) {
		// do nothing
	}

	@Override
	public void connectionLogoutRequest(ConnectionEvent arg0) {
		// do nothing
	}

	@Override
	public void connectionSentBreak(ConnectionEvent arg0) {
		// do nothing
	}

	@Override
	public void connectionTimedOut(ConnectionEvent arg0) {
		ConnectionData connectionData = arg0.getSource().getConnectionData();
		
		this.done = true;
		
		closeConnection();
		
		// ********************** Log **********************
		IgxLog.messageLog.info(this,
						"TelnetD(detected connection timeout): closed terminal({}:{}) connection.",
						connectionData.getHostName(), connectionData.getHostAddress());
		// ********************** Log **********************
	}
	
	private synchronized void closeConnection() {
		if (null != this.connection) {
			Socket socket = this.connection.getConnectionData().getSocket();
			if (null != socket) {
				try {
					socket.close();
				} catch (IOException e) {}
			}
			
			this.connection.close();
		}
	}
	
	// --- getter and setter ---

	public void setShellCommandHandler(ShellCommandHandler shellCommandHandler) {
		this.shellCommandHandler = shellCommandHandler;
	}
}
